﻿using System;
using System.Linq;
using System.Reflection;
using System.Globalization;
using System.Windows;
using Microsoft.Phone.Controls;
using WindowsPhoneMvp.Navigation;
using WindowsPhoneMvp.Navigation.Parsing;

namespace WindowsPhoneMvp.Binder.PresenterFactory
{
    /// <summary>
    /// Provides a default implementation of <see cref="IPresenterFactory"/>.
    /// </summary>
    public class DefaultPresenterFactory : IPresenterFactory
    {
        private static INavigationService _navigationService;

        /// <summary>
        /// Sets the navigation service used for presenters created with the DefaultPresenterFactory.
        /// </summary>
        public static INavigationService NavigationService
        {
            private get
            {
                if (_navigationService == null)
                {
                    var rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
                    if(rootFrame == null)
                        throw new NotSupportedException("Failed to create the default Navigation service: Application.Current.RootVisual could not be cast to PhoneApplicationFrame.");
                    _navigationService = new Wp7NavigationService(rootFrame, new DefaultViewFinder(), new NavigationParser());
                }
                return _navigationService;
            }
            set
            {
                if(value == null)
                    throw new ArgumentNullException("value", "Navigation service cannot be set to null.");
                _navigationService = value;
            }
        }

        /// <summary>
        /// Creates a new instance of the specific presenter type, for the specified
        /// view type and instance.
        /// </summary>
        /// <param name="presenterType">The type of presenter to create.</param>
        /// <param name="viewType">The type of the view as defined by the binding that matched.</param>
        /// <param name="viewInstance">The view instance to bind this presenter to.</param>
        /// <returns>An instantitated presenter.</returns>
        public IPresenter Create(Type presenterType, Type viewType, IView viewInstance)
        {
            if (presenterType == null)
                throw new ArgumentNullException("presenterType");

            if (viewType == null)
                throw new ArgumentNullException("viewType");

            if (viewInstance == null)
                throw new ArgumentNullException("viewInstance");

            try
            {
                var constructor = presenterType.GetConstructors()
                    .Where(x => x.GetParameters().Any(y => typeof(IView).IsAssignableFrom(y.ParameterType)))
                    .FirstOrDefault(x => x.GetParameters().Any(y => y.ParameterType == typeof (INavigationService)));

                IPresenter presenter;

                if (constructor != null)
                    presenter = (IPresenter)constructor.Invoke(new object[] { viewInstance, NavigationService });
                else
                {
                    presenter = (IPresenter) Activator.CreateInstance(presenterType, viewInstance);
                    if (presenter.Navigate == null) presenter.Navigate = NavigationService;
                }

                return presenter;
            }
            catch (Exception ex)
            {
                var originalException = ex;
                
                if (ex is TargetInvocationException && ex.InnerException != null)
                    originalException = ex.InnerException;

                throw new InvalidOperationException
                (
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "An exception was thrown whilst trying to create an instance of {0}. Check the InnerException for more information.",
                        presenterType.FullName),
                    originalException
                );
            }
        }

        /// <summary>
        /// Releases the specified presenter from any of its lifestyle demands.
        /// </summary>
        /// <param name="presenter">The presenter to release.</param>
        public void Release(IPresenter presenter)
        {
            var disposablePresenter = presenter as IDisposable;
            if (disposablePresenter != null)
            {
                disposablePresenter.Dispose();
            }
        }
    }
}